#include "stdafx.h"
#include "libraries.h"

#include "Application.h"
#include "Filesystem.h"
#include "Vertices.h"
#include "GpuBufferManipulator.h"
#include "Renderer.h"
#include "Engine.h"
#include "Camera.h"
#include "Timer.h"

using namespace Cvekas;

class RenderableCube : public Renderable
{
public:
	RenderableCube()
	{
		Engine engine;

		vb = engine.getVideoDevice()->getGpuBufferManager()->getVertexBuffer("cube_vb");
		ib = engine.getVideoDevice()->getGpuBufferManager()->getIndexBuffer("cube_ib");
		effect = engine.getVideoDevice()->getEffectManager()->get("Cube");

		batch.priority = 0;
		batch.batch_id = 0;
		batch.vb_id = vb->getId();
		batch.ib_id = ib->getId();
		batch.effect_id = effect->getId();
		batch.pass = 0;
		batch.start_index = 0;
		batch.tri_count = 12;
		batch.owner = this;

		world = Matrix::identity();
	}

	virtual uint getBatches(Batch** batches)
	{
		*batches = &batch;
		return 1;
	}

	virtual void onPreRender(uint16 batch_id)
	{
		Engine e;
		e.getVideoDevice()->getRenderingParams().setPerBatch(world, 0);
		effect->applyAutoParams();
		effect->commitChanges();
	}

private:
	VertexBufferPtr vb;
	IndexBufferPtr ib;
	EffectPtr effect;

	Batch batch;
	Matrix world;
};

class Cube: public Application
{
public:
	Cube(HINSTANCE instance, const std::string& name, const std::string& log_filename)
		: Application(instance, name, log_filename), 
		camera(Vector3(0.0f, 0.0f, 0.0f),
			   Vector3(0.0f, 0.0f, 0.0f),
			   Vector3(0.0f, 1.0, 0.0f))
	{
		Filesystem::init();
	}

	virtual ~Cube()
	{
	}

	virtual void initVideoDevice()
	{
		settings->insert("video", VideoSettings());

		(*settings)["video"]["ScreenWidth"] = 800;
		(*settings)["video"]["ScreenHeight"] = 600;
		(*settings)["video"]["VSync"] = false;
		(*settings)["video"]["Fullscreen"] = false;

		window = WindowPtr(new Window(instance, name));
		video_device->init(window);
		video_device->setProjectionMatrix(Matrix::perspective(pi / 4.0f, 800.0f / 600.0f, 1.0f, 80.0f));
	}

	virtual void init()
	{
		effect_manager->load("Cube", Filesystem::redirect("Cube.fx"));

		VertexBufferPtr cube_vb = gpu_buffer_manager->createVertexBuffer("cube_vb", VertexPosColor::getFormat(), 8, false);
		
		// Generate cube geometry
		
		GpuBufferManipulator<VertexPosColor> cube_vb_manipulator(cube_vb);

		cube_vb_manipulator.set(0, VertexPosColor(Vector3(-1.0f, 1.0f, -1.0f), Color(255, 0, 0)));
		cube_vb_manipulator.set(1, VertexPosColor(Vector3(1.0f, 1.0f, -1.0f), Color(0, 255, 0)));
		cube_vb_manipulator.set(2, VertexPosColor(Vector3(-1.0f, -1.0f, -1.0f), Color(0, 0, 255)));
		cube_vb_manipulator.set(3, VertexPosColor(Vector3(1.0f, -1.0f, -1.0f), Color(255, 255, 0)));

		cube_vb_manipulator.set(4, VertexPosColor(Vector3(-1.0f, 1.0f, 1.0f), Color(255, 0, 255)));
		cube_vb_manipulator.set(5, VertexPosColor(Vector3(1.0f, 1.0f, 1.0f), Color(0, 255, 255)));
		cube_vb_manipulator.set(6, VertexPosColor(Vector3(-1.0f, -1.0f, 1.0f), Color(255, 255, 255)));
		cube_vb_manipulator.set(7, VertexPosColor(Vector3(1.0f, -1.0f, 1.0f), Color(0, 0, 0)));
		cube_vb_manipulator.apply();

		IndexBufferPtr cube_ib = gpu_buffer_manager->createIndexBuffer("cube_ib", true, 36, false);
		GpuBufferManipulator<uint16> cube_ib_manipulator(cube_ib);

		cube_ib_manipulator.set(0, 0);
		cube_ib_manipulator.set(1, 1);
		cube_ib_manipulator.set(2, 2);
		cube_ib_manipulator.set(3, 1);
		cube_ib_manipulator.set(4, 3);
		cube_ib_manipulator.set(5, 2);

		cube_ib_manipulator.set(6, 0);
		cube_ib_manipulator.set(7, 4);
		cube_ib_manipulator.set(8, 5);
		cube_ib_manipulator.set(9, 5);
		cube_ib_manipulator.set(10, 1);
		cube_ib_manipulator.set(11, 0);

		cube_ib_manipulator.set(12, 4);
		cube_ib_manipulator.set(13, 6); 
		cube_ib_manipulator.set(14, 5);
		cube_ib_manipulator.set(15, 5);
		cube_ib_manipulator.set(16, 6);
		cube_ib_manipulator.set(17, 7);

		cube_ib_manipulator.set(18, 6);
		cube_ib_manipulator.set(19, 3); 
		cube_ib_manipulator.set(20, 7);
		cube_ib_manipulator.set(21, 6);
		cube_ib_manipulator.set(22, 2);
		cube_ib_manipulator.set(23, 3);

		cube_ib_manipulator.set(24, 1);
		cube_ib_manipulator.set(25, 5);
		cube_ib_manipulator.set(26, 3);
		cube_ib_manipulator.set(27, 5);
		cube_ib_manipulator.set(28, 7);
		cube_ib_manipulator.set(29, 3);

		cube_ib_manipulator.set(30, 0);
		cube_ib_manipulator.set(31, 2);
		cube_ib_manipulator.set(32, 4);
		cube_ib_manipulator.set(33, 4);
		cube_ib_manipulator.set(34, 2);
		cube_ib_manipulator.set(35, 6);

		cube_ib_manipulator.apply();

		cube = new RenderableCube();
	}

	virtual void close()
	{
		delete cube;
	}

	virtual bool update()
	{
		// Move camera in a circle
		float t = Timer::getTimeMs() / 1000.0f;
		Vector3 camera_pos(sin(t) * 6.0f, sin(t/4)*5.0f, cos(t) * 6.0f);

		camera.setPosition(camera_pos);
		camera.setDirection(-camera_pos);

		return true;
	}

	virtual bool render()
	{
		video_device->getRenderingParams().setCamera(camera);
		
		video_device->beginScene();
		main_renderer->prepare(*cube);
		main_renderer->render();
		video_device->endScene();
		main_renderer->endFrame();

		return true;
	}
private:
	SimpleCamera camera;
	RenderableCube* cube;
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nShowCmd)
{
	try
	{
		Cube app(hInstance, "Cube", "cube.log");
		app.run();
	}
	catch (std::exception& e)
	{
		MessageBox(NULL, e.what(), "Fatal error", NULL);
	}
	return 0;
}
